//
// Created by Göksu Güvendiren on 2019-05-14.
//

#include "Scene.hpp"

void Scene::buildBVH() {
    printf(" - Generating BVH...\n\n");
    this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

void Scene::sampleLight(Intersection &pos, float &pdf) const
{
    float emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
        }
    }
    float p = get_random_float() * emit_area_sum;
    emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
            if (p <= emit_area_sum){
                objects[k]->Sample(pos, pdf);
                break;
            }
        }
    }
}

//此函数已废弃
bool Scene::trace(
        const Ray &ray,
        const std::vector<Object*> &objects,
        float &tNear, uint32_t &index, Object **hitObject)
{
    *hitObject = nullptr;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        float tNearK = kInfinity;
        uint32_t indexK;
        Vector2f uvK;
        if (objects[k]->intersect(ray, tNearK, indexK) && tNearK < tNear) {
            *hitObject = objects[k];
            tNear = tNearK;
            index = indexK;
        }
    }


    return (*hitObject != nullptr);
}

// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray& ray, int depth) const
{
    Intersection inter = intersect(ray);
	if (!inter.happened) return Vector3f();
	if (inter.m->hasEmission()) {//如果与光源相交
		return inter.m->getEmission();
	}
	Vector3f L_dir(0, 0, 0), L_indir(0, 0, 0);

	Intersection light_inter;
	float pdf_light;
	sampleLight(light_inter, pdf_light);
    Vector3f wi = ray.direction;
	Vector3f p = inter.coords;
	Vector3f x = light_inter.coords;
	Vector3f x_p = x - p;
	Vector3f ws = x_p.normalized();
	Vector3f N = inter.normal;
	Vector3f NN = light_inter.normal;
	Ray test(p, ws);
	Intersection test_inter = intersect(test);
    auto fr = inter.m->eval(wi, ws, N);
	if (test_inter.distance - x_p.norm() > -0.005) {
		L_dir = light_inter.emit * fr * dotProduct(ws, N) * dotProduct(-ws, NN) / dotProduct(x_p, x_p) / pdf_light;
	}
	if (get_random_float() > RussianRoulette)return L_dir;

	Vector3f wo = inter.m->sample(wi, N).normalized();
	Ray out(p, wo);
	auto out_inter = intersect(out);
	if (out_inter.happened && !inter.m->hasEmission()) {
		float pdf = inter.m->pdf(wi, wo, N);
		auto fr = inter.m->eval(wi, wo, N);
		L_indir = castRay(out, depth + 1) * fr * dotProduct(wo, N) / pdf / RussianRoulette;
	}
	return L_indir + L_dir;
}
